using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Reflection;

namespace LowLevelGraphics.ImageFormats
{
    /// <summary>
    /// Header structure for pcx files
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    class PCXHeader
    {
        public char ID;
        public char Version;
        public char Encoding;
        public char BitPerPixel;
        public short X1;
        public short Y1;
        public short X2;
        public short Y2;
        public short HRes;
        public short VRes;
        public char[] ClrMap = new char[16 * 3];
        public char Reserved1;
        public char NumPlanes;
        public short BPL;
        public short Pal_t;
        public char[] Filler = new char[58];
    }

    /// <summary>
    /// Use this class to load pcx files
    /// </summary>
    public class PCXLoader : AbstractImageFormat, IImageFormat
    {
        private PCXHeader sHeader = new PCXHeader();
        protected Bitmap m_Bitmap = null;
        protected BitmapData pbRawBitmap = null;

        protected char[] pabFileData = null;
        
        /// <summary>
        /// Default constructor
        /// </summary>
        public PCXLoader()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PCXLoader"/> class.
        /// </summary>
        /// <param name="_sFileName">Name of the _s file.</param>
        public PCXLoader(string _sFileName)
        {
            if (!System.IO.File.Exists(_sFileName))
                return;

            long lFileSize = new FileInfo(_sFileName).Length;
            BinaryReader reader = new BinaryReader(new FileStream(_sFileName, FileMode.Open));
            pabFileData = new char[lFileSize];
            reader.Read(pabFileData, 0, (int)lFileSize);
            reader.Close();

            sHeader.ID = pabFileData[0];
            sHeader.Version = pabFileData[1];
            sHeader.Encoding = pabFileData[2];
            sHeader.BitPerPixel = pabFileData[3];
            sHeader.X1 = (short)(pabFileData[4] * 256 + pabFileData[5]);
            sHeader.Y1 = (short)(pabFileData[6] * 256 + pabFileData[7]);
            sHeader.X2 = (short)(pabFileData[8] * 256 + pabFileData[9]);
            sHeader.Y2 = (short)(pabFileData[10] * 256 + pabFileData[11]);
            sHeader.HRes = (short)pabFileData[12];
            sHeader.VRes = (short)pabFileData[13];
            sHeader.ClrMap = GetChars(14, 16 * 3);
            sHeader.Reserved1 = pabFileData[61];
            sHeader.NumPlanes = (char)4;//pabFileData[62];
            sHeader.BPL = (short)pabFileData[64];
            sHeader.Pal_t = (short)pabFileData[65];
            sHeader.Filler = GetChars(66, 58);

            // Each scan line MUST have a size that can be divided by a 'long' data type
            int iScanLineSize = sHeader.NumPlanes * sHeader.BPL;
            long sDivResult = iScanLineSize % sizeof(long);
            if (sDivResult > 0)
                iScanLineSize = (iScanLineSize / sizeof(long) + 1) * sizeof(long);

            // Set the bitmap size data member
            Size BitmapSize = new Size(sHeader.X2 - sHeader.X1 + 1, sHeader.Y2 - sHeader.Y1 + 1);
            long clImageSize = iScanLineSize * BitmapSize.Height;

            // Get the compressed image
            long lDataPos = 0;
            long lPos = 128;     // That's where the data begins

            m_Bitmap = new Bitmap(BitmapSize.Width, BitmapSize.Height);
            unsafe
            {
                pbRawBitmap = m_Bitmap.LockBits(new Rectangle(0, 0, BitmapSize.Width, BitmapSize.Height), ImageLockMode.ReadWrite, PixelFormat.Format16bppArgb1555);
                byte* pabRawBitmap = (byte*)(pbRawBitmap.Scan0);
                for (int iY = 0; iY < BitmapSize.Height; iY++)
                {
                    // Decompress the scan line
                    for (int iX = 0; iX < sHeader.BPL; )
                    {
                        uint uiValue = pabFileData[lPos++];
                        if (uiValue > 192)
                        {  // Two high bits are set = Repeat
                            uiValue -= 192;                  // Repeat how many times?
                            byte Color = (byte)pabFileData[lPos++];  // What color?

                            if (iX <= BitmapSize.Width)
                            {  // Image data.  Place in the raw bitmap.
                                for (byte bRepeat = 0; bRepeat < uiValue; bRepeat++)
                                {
                                    pabRawBitmap[lDataPos++] = Color;
                                    iX++;
                                }
                            }
                            else
                                iX += (int)uiValue; // Outside the image.  Skip.
                        }
                        else
                        {
                            if (iX <= BitmapSize.Width)
                                pabRawBitmap[lDataPos++] = (byte)uiValue;
                            iX++;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// returns Extension
        /// </summary>
        public override string Extension
        {
            get { return "pcx"; }
        }

        /// <summary>
        /// Gets the bitmap.
        /// </summary>
        /// <value>The bitmap.</value>
        public UnsafeBitmap Bitmap
        {
            get { return this.m_Bitmap; }
        }

        /// <summary>
        /// Gets the chars.
        /// </summary>
        /// <param name="_nOffset">The _n offset.</param>
        /// <param name="_nCount">The _n count.</param>
        /// <returns></returns>
        public char[] GetChars(int _nOffset, int _nCount)
        {
            char[] aChBuffer = new char[_nCount + 1];
            int nIndex = 0;
            for (int i = _nOffset; i < _nOffset + _nCount; i++)
            {
                aChBuffer[nIndex++] = pabFileData[i];
            }
            return aChBuffer;
        }

        /// <summary>
        /// Save
        /// </summary>
        /// <param name="_sFileName"></param>
        /// <returns></returns>
        public override bool Save(string _sFileName)
        {
            throw new NotImplementedException();
        }
    }
}
